#include <aer.hpp>
#include <misc/aerShape.hpp>

#include <cuda_gl_interop.h>
#include "cuda_tools.hpp"



class CudaGLComputeApp : public aer::Application
{
  public:
    static const aer::U32 kSize = 512u;
    static const aer::U32 kBlockDim = 16u;
    static const aer::I32 kDefaultKernelRadius = 8;
    
    // Note : Kernel radius can be set to kBlockDim max but setting this
    //        will affect shared memory occupancy, optimized here.
    //        (testable by doubling the max kernel radius below)
    static const aer::I32 kMaxKernelRadius = kDefaultKernelRadius;
    
    
  private:
    aer::FreeCamera *m_camera;    

    // FrameBuffer variable used to render the
    // scene into a texture
    struct {
      aer::FrameBuffer fbo;
      aer::Texture     texRGBA;
      aer::Texture     texDEPTH;
    } m_firstPass;

    // Variables used for transferring result between GPU's kernel 
    // and OpenGL
    struct {
      cudaGraphicsResource_t texSRC_CUDAResource;
      cudaGraphicsResource_t texDST_CUDAResource;
      aer::Texture           texDST;
      GLuint                 query;
      CUDATimer              cudaTimer;
    } m_interop;

    // Program Shaders 
    aer::Program m_scenePgm;        // Render default scene
    aer::Program m_computePgm;      // Compute Shader kernel
    aer::Program m_screenPgm;       // Map final texture to screen

    // Scene object
    aer::SphereRaw m_sceneObject;
    
    // Params
    aer::U32 m_kernelRadius;

    bool m_bDisplayStats;
    bool m_bUseCUDA;
    
    
  public:
    CudaGLComputeApp() 
      : m_camera(NULLPTR),
        m_kernelRadius(kDefaultKernelRadius),
        m_bDisplayStats(false),
        m_bUseCUDA(false)
    {}
    
    ~CudaGLComputeApp()
    {
      deinit();
    }
    
    
    void updateFrame();
    void drawFrame();
      
  
  private:
    void init();
    void deinit();

    void initGeometry();
    void initTexture();
    void initShader();

    void renderScene();
    void renderPostProcess();

    void launchGLCompute();
    void launchCUDAKernel();
};
